package com.atguigu.gulimall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.to.es.SkuEsModel;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.search.config.GulimallElasticsearchConfig;
import com.atguigu.gulimall.search.constant.EsConstant;
import com.atguigu.gulimall.search.feign.ProductFeignService;
import com.atguigu.gulimall.search.service.MallSearchService;
import com.atguigu.gulimall.search.vo.AttrResponseVo;
import com.atguigu.gulimall.search.vo.BrandVo;
import com.atguigu.gulimall.search.vo.SearchParam;
import com.atguigu.gulimall.search.vo.SearchResult;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.TotalHits;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @ClassName MallSearchServiceImpl
 * @Description TODO
 * @Detail detail
 * @Author MyPC
 * @Date 2020/7/8
 * @Version 1.0
 */
@Service
public class MallSearchServiceImpl implements MallSearchService {

    @Autowired
    RestHighLevelClient restHighLevelClient;

    @Autowired
    ProductFeignService productFeignService;
    /**
     * 接收所有的查询条件，根据这些查询条件来查询出所有的商品记录
     * @param searchParam
     * @return
     */
    @Override
    public SearchResult search(SearchParam searchParam) {
        SearchResult result=null;
        //1、创建检索对象,构建检索请求
        SearchRequest searchRequest=buildSearchRequest(searchParam);
        try {
            //执行检索
            SearchResponse response = restHighLevelClient.search(searchRequest, GulimallElasticsearchConfig.COMMON_OPTIONS);
            //获取封装后的检索结果
            result=buildSearchResult(response,searchParam);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //返回结果
        return result;
    }

    /**
     * 构建查询对象
     * @param searchParam
     * @return
     */
    private SearchRequest buildSearchRequest(SearchParam searchParam) {
        //1、构建SearchSourceBuilder
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //2构建bool-query，所有多条件查询，所有查询条件都要放在bool查询中
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //2.1构建match-全文检索，即根据关键字检索
        if(!StringUtils.isEmpty(searchParam.getKeyword())){
            boolQuery.must(QueryBuilders.matchQuery("skuTitle",searchParam.getKeyword()));
        }
        //2.2构建过滤条件-精确过滤单值
        if(searchParam.getCatelog3Id()!=null){
            boolQuery.filter(QueryBuilders.termQuery("catalogId",searchParam.getCatelog3Id()));
        }
        //2.3构建过滤条件-精确过滤多值
        if(searchParam.getBrandId()!=null && searchParam.getBrandId().size() > 0){
            boolQuery.filter(QueryBuilders.termsQuery("brandId",searchParam.getBrandId()));
        }
        //2.4构建过滤条件-过滤嵌入式检索属性,格式 attrs=1_其他:安卓&attrs=2_2.5寸:5寸
        if(searchParam.getAttrs()!=null && searchParam.getAttrs().size() > 0){
            //遍历每个检索属性
            for (String attrStr : searchParam.getAttrs()) {
                //遍历每个嵌入式属性，并封装检索条件
                BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
                String[] s1 = attrStr.split("_");
                String attrId=s1[0];
                String[] attrValues = s1[1].split(":");
                nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrId",attrId));
                nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None);
                //将过滤条件加入bool中
                boolQuery.filter(nestedQuery);
            }
        }
        //2.5构建过滤条件-精确过滤单值
        //0代表无库存，1代表有库存
        if(searchParam.getHasStock() != null){
            boolQuery.filter(QueryBuilders.termsQuery("hasStock",searchParam.getHasStock()==1));
        }
        //2.6构建过滤条件-精确过滤范围区间
        if(!StringUtils.isEmpty(searchParam.getSkuPrice())){
            //前端传递过来的是字符串，格式是1_500、_500、500_
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
            String[] split = searchParam.getSkuPrice().split("_");
            if(split.length == 2){
                //链式调用
                rangeQuery.gte(split[0]).lte(split[1]);
            }else {
                if(searchParam.getSkuPrice().startsWith("_")){
                    rangeQuery.lte(split[0]);
                }
                if(searchParam.getSkuPrice().endsWith("_")){
                    rangeQuery.gte(split[0]);
                }
            }
            //将过滤条件加入bool中
            boolQuery.filter(rangeQuery);
        }
        //3、构建源对象封装所有查询条件
        sourceBuilder.query(boolQuery);
        //4、排序
        if(!StringUtils.isEmpty(searchParam.getSort())){
            //排序字符串格式 sort=hotScore_desc/abs
            String[] split = searchParam.getSort().split("_");
            SortOrder order=split[1].equalsIgnoreCase("asc")?SortOrder.ASC:SortOrder.DESC;
            sourceBuilder.sort(split[0], order);
        }
        //5、分页,页码默认是第一页， 每页起始索引（页码-1）* 每页记录数
        sourceBuilder.from((searchParam.getPageNum()-1)*EsConstant.PRODUCT_PAGESIZE);
        sourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);
        //6、高亮
        if(!StringUtils.isEmpty(searchParam.getKeyword())){
            HighlightBuilder highlightBuilder=new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");
            sourceBuilder.highlighter(highlightBuilder);
        }
        //7、聚合分析
        //7.1、brand_id_agg品牌id聚合
        TermsAggregationBuilder brand_id_agg = AggregationBuilders.terms("brand_id_agg").field("brandId").size(50);
        //7.1.1品牌id的brand_name_agg品牌名子聚合
        brand_id_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));
        //7.1.2品牌id的brand_name_agg品牌图片子聚合
        brand_id_agg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1));
        //7.1.3 聚合加入构建源对象中
        sourceBuilder.aggregation(brand_id_agg);
        //7.2、catelog_id_agg分类id聚合
        TermsAggregationBuilder catelog_id_agg = AggregationBuilders.terms("catelog_id_agg").field("catalogId").size(20);
        //7.2.1、分类id的catelog_name_agg分类名子聚合
        catelog_id_agg.subAggregation(AggregationBuilders.terms("catelog_name_agg").field("catalogName").size(1));
        //7.2.2 聚合加入构建源对象中
        sourceBuilder.aggregation(catelog_id_agg);
        //7.3、属性聚合 attr_agg
        NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");
        //7.3.1、属性id子聚合 attr_id_agg
        TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");
        //7.3.1.1、属性id聚合的子聚合属性名聚合 attr_name_agg
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
        //7.3.1.2、属性id聚合的子聚合属性值聚合 attr_value_agg
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));
        //attr_id_agg设置子聚合之后才传入attr_agg中
        attr_agg.subAggregation(attr_id_agg);
        sourceBuilder.aggregation(attr_agg);
        System.out.println(sourceBuilder.toString());
        //创建检索对象，需要指定索引和传入构建源对象
        return new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX},sourceBuilder);
    }

    /**
     * 封装返回检索结果
     * @param response
     * @return
     */
    private SearchResult buildSearchResult(SearchResponse response,SearchParam searchParam) {
        SearchResult result=new SearchResult();
        SearchHits hits = response.getHits();
        SearchHit[] searchHits = hits.getHits();
        ArrayList<SkuEsModel> skuEsModels = new ArrayList<>();
        //1、封装商品信息
        if(searchHits!=null && searchHits.length>0){
            //遍历封装SkuEsModel，由于_source属性封装的就是SkuEsModel对象，所以直接将Json字符串转成对象即可
            for (SearchHit searchHit : searchHits) {
                String sourceAsString = searchHit.getSourceAsString();
                SkuEsModel skuEsModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
                if(!StringUtils.isEmpty(searchParam.getKeyword())){
                    HighlightField skuTitle = searchHit.getHighlightFields().get("skuTitle");
                    //标题只有一个，故取出第一个即可
                    String highLightTitle = skuTitle.getFragments()[0].string();
                    //用高亮的标题替换原来的标题
                    skuEsModel.setSkuTitle(highLightTitle);
                }
                skuEsModels.add(skuEsModel);
            }
        }
        result.setProducts(skuEsModels);
        //2、封装聚合信息
        //2.1、封装检索属性
        ParsedNested attr_agg = response.getAggregations().get("attr_agg");
        ParsedLongTerms attr_id_agg = attr_agg.getAggregations().get("attr_id_agg");
        ArrayList<SearchResult.AttrVo> attrVos = new ArrayList<>();
        //聚合中所有的值都是从bucket获取
        for (Terms.Bucket bucket : attr_id_agg.getBuckets()) {
            SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
            //属性id
            long attrId = bucket.getKeyAsNumber().longValue();
            //属性名，一个属性id只对应一个属性名
            String attrName = ((ParsedStringTerms) bucket.getAggregations().get("attr_name_agg")).getBuckets().get(0).getKeyAsString();
            ParsedStringTerms attr_value_agg = bucket.getAggregations().get("attr_value_agg");
            //一个属性的属性值可以有多个，是一个集合
            List<String> attrValue = attr_value_agg.getBuckets().stream().map(item -> {
                String keyAsString = item.getKeyAsString();
                return keyAsString;
            }).collect(Collectors.toList());
            attrVo.setAttrId(attrId);
            attrVo.setAttrName(attrName);
            attrVo.setAttrValue(attrValue);
            attrVos.add(attrVo);
        }
        result.setAttrs(attrVos);
        //2.2、封装品牌信息
        ParsedLongTerms brand_id_agg = response.getAggregations().get("brand_id_agg");
        ArrayList<SearchResult.BrandVo> brandVos = new ArrayList<>();
        for (Terms.Bucket bucket : brand_id_agg.getBuckets()) {
            SearchResult.BrandVo brandVo=new SearchResult.BrandVo();
            String brandId = bucket.getKeyAsString();
            String brandImg = ((ParsedStringTerms)bucket.getAggregations().get("brand_img_agg")).getBuckets().get(0).getKeyAsString();
            String brandName = ((ParsedStringTerms)bucket.getAggregations().get("brand_name_agg")).getBuckets().get(0).getKeyAsString();
            //设置品牌信息
            brandVo.setBrandId(Long.parseLong(brandId));
            brandVo.setBrandImg(brandImg);
            brandVo.setBrandName(brandName);
            brandVos.add(brandVo);
        }
        result.setBrands(brandVos);
        //2.3、封装分类信息
        ParsedLongTerms catelog_id_agg = response.getAggregations().get("catelog_id_agg");
        ArrayList<SearchResult.CateLogVo> cateLogVos = new ArrayList<>();
        for (Terms.Bucket bucket : catelog_id_agg.getBuckets()) {
            SearchResult.CateLogVo cateLogVo=new SearchResult.CateLogVo();
            String keyAsString = bucket.getKeyAsString();
            //设置分类id
            cateLogVo.setCatalogId(Long.parseLong(keyAsString));
            //设置分类名
            ParsedStringTerms catelog_name_agg = bucket.getAggregations().get("catelog_name_agg");
            String catelogName = catelog_name_agg.getBuckets().get(0).getKeyAsString();
            cateLogVo.setCatalogName(catelogName);
            cateLogVos.add(cateLogVo);
        }
        result.setCatelogs(cateLogVos);
        //3、封装分页信息
        //3.1封装当前页
        Integer pageNum=searchParam.getPageNum();
        result.setPageNum(pageNum);
        //3.2封装总记录数
        long total = hits.getTotalHits().value;
        result.setTotal(total);
        //3.3封装总页码,总记录数取模每页记录数，如果有余数，则页码加1
        int totalPages=(int) total%EsConstant.PRODUCT_PAGESIZE == 0 ? (int)total/EsConstant.PRODUCT_PAGESIZE : ((int)total/EsConstant.PRODUCT_PAGESIZE + 1);
        //3.4封装页码数组
        ArrayList<Integer> pageNavs = new ArrayList<>();
        for (int i=1;i<=totalPages;i++){
            pageNavs.add(i);
        }
        result.setPageNavs(pageNavs);
        result.setTotalPages(totalPages);
        //4、封装检索属性面包屑
        //4.1、分析每个传来的检索的属性
        List<String> attrs = searchParam.getAttrs();
        if(attrs != null && attrs.size() > 0){
            List<SearchResult.NavVo> navVos = attrs.stream().map(attr -> {
                SearchResult.NavVo navVo = new SearchResult.NavVo();
                String[] split = attr.split("_");
                //设置属性值
                navVo.setNavValue(split[1]);
                //远程调用获取属性名
                R r = productFeignService.attrInfo(Long.parseLong(split[0]));
                //收集检索条件包含的属性id
                result.getAttrIds().add(Long.parseLong(split[0]));
                if(r.getCode() == 0){
                    //远程调用成功，则设置属性名
                    AttrResponseVo attrResponseVo = r.getData("attr", new TypeReference<AttrResponseVo>() {
                    });
                    navVo.setNavName(attrResponseVo.getAttrName());
                }else {
                    navVo.setNavName(split[0]);
                }
                //4.2、取消面包屑的属性检索词后的跳转
                //替换当前遍历的属性查询条件，实现前端面包屑的取消
                String replace = replaceQueryString(searchParam, attr,"attrs");
                navVo.setLink("http://search.gulimall.com/search.html?"+replace);
                return navVo;
            }).collect(Collectors.toList());
            result.setNavs(navVos);
        }
        //5、封装品牌面包屑
        List<Long> brandIds = searchParam.getBrandId();
        if(brandIds != null && brandIds.size() > 0){
            List<SearchResult.NavVo> navVos=result.getNavs();
            SearchResult.NavVo navVo=new SearchResult.NavVo();
            navVo.setNavName("品牌");
            //远程调用商品服务根据id数组获取品牌值
            R infos = productFeignService.brandInfos(brandIds);
            if(infos.getCode() == 0){
                List<BrandVo> brands = infos.getData("brands", new TypeReference<List<BrandVo>>() {
                });
                StringBuffer buffer=new StringBuffer();
                String replace="";
                for (BrandVo brand : brands) {
                    buffer.append(brand.getName()+";");
                    replace = replaceQueryString(searchParam, brand.getBrandId()+"","brandId");
                }
                navVo.setNavValue(buffer.toString());
                navVo.setLink("http://search.gulimall.com/search.html?"+replace);
            }
            navVos.add(navVo);
        }
        //TODO 5、封装分类面包屑，不需要导航取消
        return result;
    }

    private String replaceQueryString(SearchParam searchParam, String value,String name) {
        String encodeAttr ="";
        try {
            encodeAttr= URLEncoder.encode(value, "utf-8");
            //java解码后，会把空格解码成+,所以这里要把+替换从%20,因为浏览器对空格的编码与java有差异
            encodeAttr.replace("+","%20");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        //替换后的查询条件,这个替换逻辑有bug，
        return searchParam.get_queryString().replace("&"+name+"=" + encodeAttr, "");
    }

}
